<?php
/**
 * Created by IntelliJ IDEA.
 * User: baiyang
 * Date: 2018/6/27
 * Time: 下午3:27
 */

namespace Core\Common\Base;

use Core\Server\SuperServer;

class Process
{
    /**
     * @var SuperServer
     */
    private $server;
    private $name;
    private $num;
    private $processes = [];
    private $handler;
    private $classname;

    public function __construct($server, $name, $num, $handler)
    {
        $this->server = $server;
        $this->name = strtolower(str_replace(['-', '\\', '.'], ['_', '_', '_'], $name));
        $this->num = (int)$num;
        $this->handler = $handler;
        $suffix = str_replace(['_'], ['\\'], ucwords($this->handler, '_')).'Handler';
        if (class_exists('\App\Handler\\' . $suffix)) {
            $this->classname = '\App\Handler\\' . $suffix;
        } else if (class_exists('\Core\Common\Handler\\' . $suffix)) {
            $this->classname = '\Core\Common\Handler\\' . $suffix;
        }
    }
    //主进程
    public function run($process)
    {
        if ('Linux' == PHP_OS) {
            $process->name(CORE_NAME . 'Service:'.$this->name.' Main Process');
        }
        file_put_contents(CORE_PID_DIR . "{$this->name}.pid", posix_getpid());
        $toRestart = [];
        //中止信号
        pcntl_signal(SIGTERM, function () use ($process) {
            if (0 == count($this->processes)) {//本身就没有子进程，直接退出
                $process->exit(1);
                return;
            }
            $sig = (defined($this->classname.'::TERM') && constant($this->classname.'::TERM')) ? SIGKILL : SIGTERM;
            foreach ($this->processes as $sub) {//通知子进程结束
                posix_kill($sub->pid, $sig);
            }
            if ($sig == SIGKILL) {
                //unlink(CORE_PID_DIR . "{$this->name}.pid");
            }
        });
        //重启信号
        pcntl_signal(SIGUSR1, function () {
            foreach ($this->processes as $sub) {
                posix_kill($sub->pid, SIGUSR1);
            }
        });
        //子进程退出信号, 需要回收资源
        pcntl_signal(SIGCHLD, function () use ($process, &$toRestart) {
            while($ret = \swoole_process::wait(false)) {
                if ($ret['code']) {//异常退出, code里填序号
                    $toRestart[] = $ret;
                    continue;
                }
                //正常退出
                unset($this->processes[$ret['pid']]);
            }
            if (0 === count($this->processes) && $process->pid == posix_getpid()) {
                $process->exit(1);
            }
        });
        $num = $this->num;
        //启动子进程
        while ($num) {
            $subProcess = $this->newSubProcess($num--);
            $pid = $subProcess->start();
            if ($pid) {
                $this->processes[$pid] = $subProcess;
            }
        }
        while (true) {
            foreach ($toRestart as $ret) {
                //只处理kill -9 和 kill -USR1的重启, signal只有-9的时候才有值
                if ($ret['code'] == SIGUSR1 || $ret['signal'] == SIGKILL) {
                    $index = $this->processes[$ret['pid']];
                    $subProcess = $this->newSubProcess($index);
                    $pid = $subProcess->start();
                    if ($pid) {
                        $this->processes[$pid] = $subProcess;
                    }
                }
                unset($this->processes[$ret['pid']]);
            }
            if (empty($this->processes)) {
                break;
            }
            $toRestart = [];
            //500毫秒
            usleep(500000);
            pcntl_signal_dispatch();
        }
        $process->exit(1);
    }

    //子进程
    public function newSubProcess($index)
    {
        return new \swoole_process(function($process) use ($index) {
            if ('Linux' == PHP_OS) {//进程名
                $process->name(CORE_NAME . 'Service:'.$this->name.' Child Process');
            }
            //handler

            if (!class_exists($this->classname)) {
                Log::error(LOG_INFO, ['message' => "class {$this->classname} not found", 'file' => __FILE__, 'line' => __LINE__]);
                return;
            }
            //进程的主方法
            $callback = [$this->classname, 'run'];
            if (!is_callable($callback)) {
                Log::error(LOG_INFO, ['message' => "{$this->classname}::run not callable", ]);
                return;
            }
            //是否需要转发给worker
            //$dispatch = (defined($this->classname.'::DISPATCH') && constant($this->classname.'::DISPATCH')) ? true : false;
            pcntl_signal(SIGUSR1, function ($signo) use ($process, $index) {//重启信号
                $process->exit(SIGUSR1);
            });
            pcntl_signal(SIGTERM, function ($signo) use ($process, $index) {//退出信号
                $process->exit(SIGTERM);
            });
            //每执行完一次, 休息的时间
            $usleep = Config::config('process.sleep.' . $this->name);
            if (empty($usleep)) {
                $usleep = Config::config('process.usleep');
            }
            if (empty($usleep)) {
                $usleep = 2000;
            }
            while (true) {//主循环
                $t1 = microtime(true);
                $errMsg = "";
                try {
                    $ret = call_user_func($callback, $this->server->instance, $process);//执行处理
                }catch (\Throwable $processEx){
                    $errMsg = $processEx->getTraceAsString();
                }
                if (0) {
                    $workerNum = $this->server->instance->setting['worker_num'];
                    //选择要分发的worker
                    if (empty($ret['key'])) {
                        $idx = mt_rand(0, $workerNum - 1);
                    } else if ($ret['key'] && (is_int($ret['key']) || ctype_digit($ret['key']))) {
                        $idx = $ret['key'] % $workerNum;
                    } else {
                        $idx = crc32($ret['key']) % $workerNum;
                    }
                    //选择taskId
                    $taskId = mt_rand(0, $this->server->instance->setting['task_worker_num'] - 1);
                    $this->server->instance->sendMessage(['cast', $ret, $idx], $workerNum + $taskId);
                }
                $used = (microtime(true) - $t1) * 1000;
                if (Log::currentLogLevel() >= LOG_INFO) {
                    Log::process(LOG_DEBUG, [
                        'name' => $this->name ,
                        'index' => $index,
                        'result' => $ret,
                        'usleep' => $usleep,
                        'ms' => $used,
                        'msg' => $errMsg,
                    ]);
                }
                usleep($usleep);
                pcntl_signal_dispatch();
            }
        });
    }
}